// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: fengyang

#pragma once

#include <string>

#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"

namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;
using coin2::exchange::base::feed_util::Index;

class BitgetFeedUpdateParser {
 public:
  static bool
  ParseBook(
      BookBuilder* book,
      const rapidjson::Document& document,
      BookBuilder::BookOption book_type) {
    CHECK_EQ(document["data"].GetArray().Size(), 1);

    for (const auto& data_json : document["data"].GetArray()) {
      int64_t ts = stoll(data_json["ts"].GetString()) * 1'000'000LL;
      auto tx = book->NewTransaction(ts);
      if (book_type == BookBuilder::SNAPSHOT) {
        tx.Clear();
      }
      for (const auto& bid_json : data_json["bids"].GetArray()) {
        tx.UpdateBid(stod(bid_json.GetArray()[0].GetString()),
                     stod(bid_json.GetArray()[1].GetString()));
      }
      for (const auto& ask_json : data_json["asks"].GetArray()) {
        tx.UpdateAsk(stod(ask_json.GetArray()[0].GetString()),
                     stod(ask_json.GetArray()[1].GetString()));
      }
    }

    return true;
  }

  static void ParseTrade(const rapidjson::Value& trade_json, Trade* trade) {
    CHECK_EQ(trade_json.GetArray().Size(), 4);
    trade->timestamp = stoll(trade_json[0].GetString()) * 1'000'000LL;
    trade->price = stod(trade_json[1].GetString());
    trade->fill_qty = stod(trade_json[2].GetString());
    const std::string& side = trade_json[3].GetString();
    if (side == "buy") {
      trade->side = coin2::exchange::feed::TRADE_BUY_SIDE;
    } else if (side == "sell") {
      trade->side = coin2::exchange::feed::TRADE_SELL_SIDE;
    }
  }

  static fastfeed::proto::KlineInterval ParseKlineInterval(std::string_view interval) {
    static std::map<std::string, fastfeed::proto::KlineInterval> interval_map = {
      {"1m", fastfeed::proto::KLINE_INTERVAL_1MIN},
      {"5m", fastfeed::proto::KLINE_INTERVAL_5MIN},
      {"15m", fastfeed::proto::KLINE_INTERVAL_15MIN},
      {"30m", fastfeed::proto::KLINE_INTERVAL_30MIN},
      {"1H", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"1Dutc", fastfeed::proto::KLINE_INTERVAL_1DAY},
      {"1Wutc", fastfeed::proto::KLINE_INTERVAL_1WEEK},
      {"1Mutc", fastfeed::proto::KLINE_INTERVAL_1MON}
    };

    auto iter = interval_map.find(std::string(interval));
    if (iter == interval_map.end()) {
      return fastfeed::proto::KLINE_INTERVAL_INVALID;
    }

    return iter->second;
  }

  static bool ParseKlineRest(
      const rapidjson::Value& msg,
      Klines* klines,
      fastfeed::proto::KlineInterval interval,
      int64_t timestamp) {
    CHECK_EQ(msg.Size(), 7);
    int64_t open_timestamp = stoll(msg[0].GetString()) * 1'000'000LL;
    auto result = klines->AddKline(open_timestamp);
    if (!result) return false;

    Kline* kline;
    bool freezed;
    std::tie(kline, freezed) = result.value();
    kline->open_timestamp = open_timestamp;
    kline->open = stod(msg[1].GetString());
    kline->high = stod(msg[2].GetString());
    kline->low = stod(msg[3].GetString());
    kline->close = stod(msg[4].GetString());
    kline->volume = stod(msg[5].GetString());
    kline->turnover = stod(msg[6].GetString());
    kline->timestamp = timestamp;
    kline->interval = interval;

    return freezed;
  }

  static void ParseIndex(
      const rapidjson::Value& msg,
      Index* index) {
    CHECK(index);
    index->Clear();
    index->timestamp = stoll(msg["timestamp"].GetString()) * 1'000'000LL;
    index->price = stod(msg["index"].GetString());
  }

  static void ParseFundingRate(
      const rapidjson::Value& msg,
      FundingRate* funding_rate) {
    CHECK(funding_rate);
    funding_rate->funding_rate = stod(msg["data"]["fundingRate"].GetString());
  }

  static void ParseFundingTime(
      const rapidjson::Value& msg,
      FundingRate* funding_rate) {
    CHECK(funding_rate);
    funding_rate->funding_time = stoll(msg["data"]["fundingTime"].GetString()) * 1'000'000LL;
  }

  static void ParseOpenInterest(
      const rapidjson::Value& msg,
      OpenInterest* open_interest) {
    open_interest->timestamp = stoll(msg["timestamp"].GetString()) * 1'000'000LL;
    open_interest->open_interest_qty = stod(msg["amount"].GetString());
  }

  static void ParseMarkPrice(
      const rapidjson::Value& msg,
      MarkPrice* mark_price) {
    CHECK(mark_price);
    mark_price->Clear();
    mark_price->timestamp = stoll(msg["timestamp"].GetString()) * 1'000'000LL;
    mark_price->mark_price = stod(msg["markPrice"].GetString());
  }
};

}  // namespace impl

using impl::BitgetFeedUpdateParser;
